// linux/kernel/blk_drv/hd.c
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/hdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#define MAJOR_NR	3
#include "blk.h"

#define CMOS_READ(addr) ({\
		outb(0x80|addr, 0x70);\
		inb(0x71);\
		})

#define MAX_ERRORS	7
#define MAX_HD		2

static void recal_intr(void);

static int recalibrate = 1;
static int reset = 1;

/* 
 * This struct defines the HD's and their types.
 */

struct hd_i_struct {
	int head;
	int sect;
	int cyl;
	int wpcom;
	int lzone;
	int ctl;
};

#ifdef HD_TYPE
struct hd_i_struct hd_info[] = {HD_TYPE};

static int NR_HD=((sizeof(hd_info))/(sizeof(struct hd_i_struct)));
#else
struct hd_i_struct hd_info[]={{0,0,0,0,0,0},{0,0,0,0,0,0}};
static int NR_HD = 0;
#endif

static struct hd_struct {
	long start_sect;
	long nr_sects;
} hd[5*MAX_HD]={{0,0},};

#define port_read(port, buf, nr) \
	__asm__("cld;rep;insw"::"d"(port),"D"(buf),"c"(nr))

#define port_write(port, buf, nr) \
	__asm__("cld;rep;outsw"::"d"(port),"S"(buf),"c"(nr))

extern void hd_interrupt(void);
extern void rd_load(void);

int sys_setup(void * BIOS)
{
	static int callable=1;
	int i, drive;
	unsigned char cmos_disks;
	struct partition *p;
	struct buffer_head *bh;
	if(!callable)  // only call this function once!
		return -1;
	callable = 0;
#ifndef HD_TYPE
	for(drive=0;drive<2;drive++) {
		hd_info[drive].cyl = *(unsigned short*)BIOS;
		hd_info[drive].head = *(unsigned char *)(BIOS+2);
		hd_info[drive].wpcom = *(unsigned short *)(BIOS+5);
		hd_info[drive].ctl = *(unsigned char *)(BIOS+8);
		hd_info[drive].lzone = *(unsigned short *)(BIOS+12);
		hd_info[drive].sect = *(unsigned char *)(BIOS+14);
		BIOS += 16;
		//debug_begin
		printk("hd_info[%d]:cyl=%d, head=%d, wpcom=%d, ctl=%d,lzone=%d, sect=%d\n\r", drive, 
				hd_info[drive].cyl, hd_info[drive].head,
				hd_info[drive].wpcom, hd_info[drive].ctl,
				hd_info[drive].lzone, hd_info[drive].sect);
		//debug_end
	}
	if(hd_info[1].cyl) // if there is only one harddisk?
		NR_HD = 2;
	else 
		NR_HD = 1;
#endif
	for(i=0;i<NR_HD;i++){
		hd[i*5].start_sect = 0;
		hd[i*5].nr_sects = hd_info[i].head * hd_info[i].sect
			* hd_info[i].cyl;
	}
	if((cmos_disks = CMOS_READ(0x12))&0xf0) {
		if(cmos_disks&0x0f)
			NR_HD = 2;
		else
			NR_HD = 1;
	}else {
		NR_HD = 0;
	}
	for(i=NR_HD;i<2;i++) {
		hd[i*5].start_sect = 0;
		hd[i*5].nr_sects = 0;
	}
	for(drive=0;drive<NR_HD;drive++) {
		if(!(bh=bread(0x0300+drive*5, 0))) {
			printk("Unable to read partition table of dirve %d\n\r", drive);
			panic("");
		}
		if(bh->b_data[510]!=0x55||(unsigned char)bh->b_data[511]!=0xAA){
			printk("Bad partition table on drive %d\n\r",drive);
			panic("");
		}
		p=0x1BE + (void*)bh->b_data;
		for(i=1;i<5;i++,p++) {
			hd[i+5*drive].start_sect = p->start_sect;
			hd[i+5*drive].nr_sects = p->nr_sects;
		}
		brelse(bh);
	}
	if(NR_HD)
		printk("Partition table%s OK.\n\r", (NR_HD>1)?"s":"");
	rd_load();
	mount_root();
	return 0;
}
//判断并循环等待驱动器就绪
static int controller_ready(void)
{
	int retries = 10000;
	while(--retries && (inb(HD_STATUS)&0xc0)!=READY_STAT);
	return retries;
}
//检测温切斯特硬盘的状态，返回0表示正常，1表示出错
static int win_result(void)
{
	int i=inb(HD_STATUS);
	if((i&(BUSY_STAT|READY_STAT|WRERR_STAT|SEEK_STAT|ERR_STAT))
			== (READY_STAT|SEEK_STAT))
		return 0;
	if(i&ERR_STAT)
		i=inb(HD_ERROR);
	return 1;
}
//向硬盘控制器发送命令块
static void hd_out(unsigned int drive, unsigned int nsect, unsigned int sect,
		unsigned int head, unsigned int cyl, unsigned int cmd, 
		void (*intr_addr)(void))
{
	register int port asm("dx");
	if(drive>1||head>15)
		panic("Trying to write bad sector!\n");
	if(!controller_ready())
		panic("HD controller not ready\n");
	do_hd = intr_addr;//where is do_hd()?
	outb(hd_info[drive].ctl, HD_CMD);
	port=HD_DATA;
	outb(hd_info[drive].wpcom>>2, ++port);
	outb(nsect, ++port);
	outb(sect, ++port);
	outb(cyl, ++port);
	outb(cyl>>8, ++port);
	outb(0xA0|(drive<<4)|head, ++port);
	outb(cmd, ++port);
}
// 等待硬盘就绪。循环等待主状态控制器忙标志位复位，若仅有就绪或寻道标志置位，
// 则成功 返回0。 若经过一段时间仍为忙， 返回1
static int drive_busy(void)
{
	unsigned int i;
	for(i=0;i<10000;i++)//循环等待就绪标志位置位
		if(READY_STAT==(inb(HD_STATUS)&(BUSY_STAT|READY_STAT)))
			break;
	i = inb(HD_STATUS);
	i&=BUSY_STAT|READY_STAT|SEEK_STAT;
	if(i==(READY_STAT|SEEK_STAT))//若有就绪或寻道结束，则返回0表示成功
		return 0;
	printk("HD controller times out\n\r");
	return 1;
}
//论断复位（重新校正）硬盘控制器
static void reset_controller(void)
{
	int i;
	outb(4, HD_CMD);//发送控制命令4表示复位
	for(i=0;i<100;i++)
		nop();
	outb(hd_info[0].ctl&0x0f, HD_CMD);//发送正常的控制字节
	if(drive_busy())
		printk("HD controller still busy\n\r");
	if((i=inb(HD_ERROR))!=1)//取错误寄存器的内容，若不等于1（无错误）则出错
		printk("HD controller reset failed:%02x\n\r", i);
}
//复位硬盘nr。首先重新校正硬盘控制器，然后发送硬盘控制器命令建立驱动器参数
static void reset_hd(int nr)
{
	reset_controller();
	hd_out(nr, hd_info[nr].sect, hd_info[nr].sect, 
			hd_info[nr].head-1, hd_info[nr].cyl, WIN_SPECIFY,
			&recal_intr);
}
//意外硬盘中断调用函数
void unexpected_hd_interrupt(void)
{
	printk("Unexpected HD interrupt!\n\r");
}
//读写硬盘失败处理函数
static void bad_rw_intr(void)
{
	if(++CURRENT->errors >= MAX_ERRORS)
		end_request(0);
	if(CURRENT->errors > MAX_ERRORS/2)
		reset = 1;
}
//读操作中断调用函数， 将在执行硬盘中断处理程序中被调用。
static void read_intr(void)
{
	if(win_result()) {
		bad_rw_intr();
		do_hd_request();
		return;
	}
	port_read(HD_DATA, CURRENT->buffer, 256);
	CURRENT->errors = 0;
	CURRENT->buffer += 512;
	CURRENT->sector ++;
	if(--CURRENT->nr_sectors) {
		do_hd = &read_intr;
		return ;
	}
	end_request(1);
	do_hd_request();
}

//写操作中断调用函数， 在硬盘中断处理程序中被调用
static void write_intr(void)
{
	if(win_result()) {
		bad_rw_intr();
		do_hd_request();
		return;
	}
	if(--CURRENT->nr_sectors) {
		CURRENT->sector++;
		CURRENT->buffer+=512;
		do_hd = &write_intr;
		port_write(HD_DATA, CURRENT->buffer, 256);
		return;
	}
	end_request(1);
	do_hd_request();
}

//硬盘重新校正（复位）中断调用函数
static void recal_intr(void)
{
	if(win_result())
		bad_rw_intr();
	do_hd_request();
}
//执行硬盘读写请求操作
void do_hd_request(void)
{
	int i, r;
	unsigned int block, dev;
	unsigned int sec, head, cyl;
	unsigned int nsect;
	INIT_REQUEST;
	dev = MINOR(CURRENT->dev);
	block = CURRENT->sector;
	if(dev>=5*NR_HD||block+2>hd[dev].nr_sects) {
		end_request(0);
		goto repeat;
	}
	block+=hd[dev].start_sect;
	dev/=5;
	__asm__("divl %4":"=a"(block),"=d"(sec):"a"(block),"l"(0),"b"(hd_info[dev].sect));
	__asm__("divl %4":"=a"(cyl), "=d"(head):"a"(block),"l"(0),"b"(hd_info[dev].head));
	sec++;
	nsect = CURRENT->nr_sectors;
	if(reset) {
		reset = 0;
		recalibrate = 1;
		reset_hd(CURRENT_DEV);
		return;
	}
	if(recalibrate) {
		recalibrate = 0;
		hd_out(dev, hd_info[CURRENT_DEV].sect, 0, 0, 0, WIN_RESTORE, &recal_intr);
		return;
	}
	if(CURRENT->cmd == WRITE) {
		hd_out(dev, nsect, sec, head, cyl, WIN_WRITE, &write_intr);
		for(i=0;i<3000&&!(r=inb(HD_STATUS)&DRQ_STAT); i++)
			/* do nothing */
			;
		if(!r) {
			bad_rw_intr();
			goto repeat;
		}
		port_write(HD_DATA, CURRENT->buffer, 256);
	}else if(CURRENT->cmd == READ) {
		hd_out(dev, nsect, sec, head, cyl, WIN_READ, &read_intr);
	}else
		panic("unknown hd-command");
}

void hd_init(void)
{
	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; //do_hd_request()
	set_intr_gate(0x2E, &hd_interrupt); //设置硬盘中断门向量
	outb(inb(0x21)&0xfb, 0x21);//复位主8259A int2 屏蔽位，允许从片发出中断
	outb(inb(0xA1)&0xbf, 0xA1);//复位硬盘中断示屏蔽位，允许
				//硬盘控制器发出中断信号
}
